<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>儿童编程</title>
</head>

<body>
<div>
<canvas id='canvas' width='800' height='600' style='width:800px;height:600px;border:1px solid #058;'></canvas>
<textarea id='program' style='width:100%;height:300px;font-size:20px;'>
</textarea>
<button onclick='parse_program();' style='width:100%;height:50px;font-size:20px;'>开始运行</button>
</div>
<script src="common.js" type="text/javascript"></script>
<script>
/*
名称：javascript制作的编译器
作者：浙江省新昌县城西小学 唐明
联系QQ:147885198
功能：对输入的代码按照自定义的一种语法进行语法分析并检测语法错误，根据代码生成虚拟计算机代码，最后模拟执行这个计算机代码。
特色：结合游戏功能，用代码指挥屏幕上的小车完成任务。
更新时间：
2015-4-28：
	1、语法检查，生成虚拟代码，执行代码
	2、俯视地图的绘制，游戏完成与否的判定。
*/
//关键词列表
var keyword=['if','else','repeat'];
var sysfunc=['go','toleft','toright','jump','read','write','check','remove','color'];
//指针
var pindex=0;
//程序代码
var program='';
//全局变量
var variable=[];
//字符集
var charset=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','-','*','/','=','!','>','<','{','}',';','(',')',' ','\r','\n','\t'];
//读取的符号类型
var symtype=0;//1==自定义标识符   2==数值   3==赋值   4==关系运算   5==算术运算   6==语句结束   7==小括号  8==大括号   9==关键字   10==系统函数
//当前读取的符号
var symbol='';
//错误信息
var errorinfo=[];
/*目标代码
调用函数   CALL 函数名 参数表达式
设置变量   SET  变量名 表达式
为0时跳转  JPC  地址   条件表达式
跳转      JMP  地址
*/
var target_pg=[];

//地图数据
var _map={
mapwidth:6,mapheight:3,
map:[[23,23,23,23,23,23],
	[0,0,8,9,0,0],
	[23,23,0,23,23,23]],
yun:[{x:4,y:1,able:1}],
bomb:[{x:5,y:1,able:1}],
remove_bomb_count:1,
startx:0,starty:1,start_dir:2,
endx:5,endy:1,end_dir:2,
success:'pos,check,color,write',
cell_width:100,cell_height:100,
data:[{x:2,y:1,min:3,max:5,value:3}],
suanfa:'map.data[0].value*3'
};
var map;
//用户角色信息
var role={startx:0,starty:0,start_dir:1};

//地面颜色列表
var colors=['#ECF4FC','#D21241','#FF7F00','#EEEE00','#228B22','#00CDCD','#4169E1','#7A378B'];

var canvas=document.getElementById('canvas');
var ctx=canvas.getContext("2d");
var img=new Array();
img[0]=new Image();
img[0].src='image/1.JPG';
img[1]=new Image();
img[1].src='image/2.JPG';
img[2]=new Image();
img[2].src='image/1.JPG';
img[3]=new Image();
img[3].src='image/4.png';
img[4]=new Image();
img[4].src='image/7.png'
img[5]=new Image();
img[5].src='image/s1.png'
img[6]=new Image();
img[6].src='image/s2.png'
img[7]=new Image();
img[7].src='image/s3.png'
img[8]=new Image();
img[8].src='image/s4.png'
img[9]=new Image();
img[9].src='image/light.png'
img[10]=new Image();
img[10].src='image/target.png'
img[11]=new Image();//云
img[11].src='image/6.png'
img[12]=new Image();//数据点
img[12].src='image/8.png'
img[13]=new Image();//数据点
img[13].src='image/p1.png'
img[14]=new Image();//数据点
img[14].src='image/p2.png'
//window.onload=draw_map(map);//图片未完成加载,导致drawImage失败

document.onreadystatechange = stateChange;

function stateChange() {
	if(document.readyState == 'complete') {
		//角色信息初始化
		map=_map;
		role.start_dir=map.start_dir;
		role.startx=map.startx;
		role.starty=map.starty;
		draw_map(map);
		draw_role();
	}
}

function getch(){
	if(pindex>program.length-1) return '';
	var ch=program.charAt(pindex);
	pindex++;
	return ch;
}

function getsym(){
	var ch=getch();
	if(ch=='') return '';
	if(charset.indexOf(ch)==-1){//读到不合格的字符
		error('含有不合格的字符：'+ch);
		return '';	
	}
	while(checksplit(ch)){
		ch=getch();
	}
	//1==标识符   2==数值   3==赋值   4==关系运算   5==算术运算   6==语句结束   7==小括号  8==大括号
	var sym='';
	if(checkletter(ch)){//字母
		while(checkletter(ch)){
			sym+=ch;
			ch=getch();	
		}
		if(ch!='') pindex--;
		symtype=1;
		symbol=sym;
		return;
	}else if(checknumber(ch)){
		while(checknumber(ch)){
			sym+=ch;
			ch=getch();	
		}
		if(ch!='') pindex--;
		symtype=1;
		symbol=sym;
		return ;
	}else if(ch=='=' || ch=='>' || ch=='<'){
		var ch1=getch();
		if(ch1=='=') {
			symtype=4;
			symbol=ch+ch1;
			return;
		}
		pindex--;//回退一个字符
		if(ch=='=') symtype=3; else symtype=4;
		symbol=ch;
		return;
	}else if(ch=='!'){
		var ch1=getch();
		if(ch1!='=') {
			error('！的后面需要跟着=');
			return '';
		}
		symtype=4;
		symbol='!=';
		return;
	}else if(ch==')' || ch=='('){
		symtype=7;
		symbol=ch;
		return ;
	}else if(ch=='}' || ch=='{'){
		symtype=8;
		symbol=ch;
		return;	
	}else if(ch==';'){
		symtype=6;
		symbol=ch;
		return ;
	}
	symtype=5;
	symbol=ch;
}

function random_x(min,max){
	return Math.ceil(Math.random()*(max-min)+min);
}

function parse_program(){
	//角色信息初始化
	role.start_dir=map.start_dir;
	role.startx=map.startx;
	role.starty=map.starty;
	map=_map;
	//初始化数据点
	var v;
	for(var i=0;i<map.data.length;i++){
		v=random_x(map.data[i].min,map.data[i].max);
		map.data[i].value=v;
	}
	pindex=0;
	errorinfo=[];
	target_pg.length=0;
	program='{'+document.getElementById('program').value+'}';
	program=program.replace(/}/g,'};');
	getsym();
	parse_statement();
	if(errorinfo.length<=0) alert('程序没有错误！');
	//console.log(target_pg);
	if(errorinfo.length<=0) do_interpret();
}

//分析代码组
function parse_statement(){
		//开始词法分析
		//1==标识符   2==数值   3==赋值   4==关系运算   5==算术运算   6==语句结束   7==小括号  8==大括号
		switch(symtype){
			case 1:
				switch(symbol){
					case 'if':
						parse_if();
						break;
					case 'repeat':
						parse_repeat();
						break;
					default:
						var ident=symbol;
						getsym();
						if(symbol=='('){//解释函数
							var p1=pindex;
							getsym();
							var op2='';
							if(symbol!=')'){
								
								parse_expression();
								op2=program.substr(p1,pindex-p1-1);
							}
							if(symbol!=')') {
								error('函数中(必须和)配对');
								return -1;	
							}
							var pg={code:'call',op1:ident,op2:op2};
							target_pg.push(pg);
							getsym();
						}else if(symbol=='='){
							var p1=pindex;
							getsym();
							parse_expression();
							var op2=program.substr(p1,pindex-p1-1);
							var pg={code:'set',op1:ident,op2:op2};
							target_pg.push(pg);
						}else {error('标识符后面需要跟上(或=');return;}
						break;	
				}
				break;
			case 6://遇到; 直接进入下一个语句
				getsym();
				return;
			case 8:
				if(symbol=='{'){
					getsym();
					if(symbol=='}') {getsym();return;}//空语句组
					parse_statement();
					while(symbol==';'){//有多个语句
						getsym();
						if(symbol=='}'){break;}
						parse_statement();	
					}
					
					if(symbol!='}') {error('{}一定要配对');return;}
					getsym();
				}
				break;
			default://表达式
				error('发生错误');
				return;
				break;	
		}
}
//分析if语句
function parse_if(){
	//记录下当前代码位置
	var pgi=target_pg.length;
	
	getsym();
	if(symbol!='(') {
		error('if后面必须跟上(');
		return -1;	
	}
	var p1=pindex;
	parse_condition();
	var op2=program.substr(p1,pindex-p1-1);
	//插入代码
	target_pg.push({code:'jpc',op1:'',op2:op2});
	if(symbol!=')') {
		error('if中(必须和)配对');
		return -1;	
	}
	getsym();
	parse_statement();
	target_pg[pgi].op1=target_pg.length;
}

function parse_condition(){
	getsym();
	parse_expression();
	if(symtype!=4){
		error('条件运算时，中间必须是== != > < >= <=');
		return -1;	
	}
	getsym();
	parse_expression();
	
}

function parse_repeat(){
	
	getsym();
	if(symbol!='(') {
		error('repeat后面必须跟上(');
		return -1;	
	}
	var p1=pindex;
	getsym();
	parse_expression();
	var op2=program.substr(p1,pindex-p1-1);
	//产生一个临时变量
	var temp_var='temp'+Math.floor(Math.random()*1000);
	target_pg.push({code:'set',op1:temp_var,op2:op2});
	var pgi=target_pg.length;//记录当前代码位置
	op2=temp_var+'>0';
	target_pg.push({code:'jpc',op1:'',op2:op2});
	if(symbol!=')') {
		error('repeat中(必须和)配对');
		return -1;	
	}
	getsym();
	parse_statement();
	target_pg.push({code:'set',op1:temp_var,op2:temp_var+'-1'});
	target_pg.push({code:'jmp',op1:pgi,op2:''});
	target_pg[pgi].op1=target_pg.length;
}


function parse_expression(){	
	parse_term();
	while(symbol=='+' || symbol=='-'){
		getsym();
		parse_term();
	}
}

function parse_term(){
	parse_factor();
	while(symbol=='*' || symbol=='/') {
		getsym();
		parse_factor();
	}
}

function parse_factor(){
	if(symtype==1){
		getsym();
		return ;
	}else if(symtype==2){
		getsym();
		return;
	}else	if(symbol=='('){
		getsym();
		parse_expression();
		if(symbol!=')') {
			error('表达式中的括号一定要匹配');
			return -1;	
		}
		getsym();
	}else{
		error('错误的表达式');
		return -1;
	}
}

function error(str){
	errorinfo[errorinfo.length]=str;
	alert(str);
}

var icode=0;
var h_timer;

function do_interpret(){
	icode=0;
	interpret();	
}
//执行中间代码
function interpret(){
	if(icode<target_pg.length){
		var pg=target_pg[icode];
		if(pg.code=='set'){
			var t=expression_value(pg.op2);
			set_variable(pg.op1,t);
			icode++;
			setTimeout(interpret,1);
		}else if(pg.code=='jpc'){
			var c=get_condition(pg.op2);
			if(c){//条件成立，执行下一条语句
				icode++;
			}else{
				icode=pg.op1;
			}
			setTimeout(interpret,1);
		}else if(pg.code=='jmp'){
			icode=pg.op1;
			setTimeout(interpret,1);
		}else if(pg.code=='call'){
			switch(pg.op1){
				case 'show':
					var v=expression_value(pg.op2);
					alert(v);
					break;
				case 'go':
					var temp=go_func();
					draw_map(map);
					draw_role();
					if(!temp){
						alert('你被炸弹炸死了！');
						return;
					}
					break;
				case 'left':
					if(role.start_dir>1) role.start_dir--;
						else role.start_dir=4;
					draw_map(map);
					draw_role();
					break;
				case 'right':
					if(role.start_dir<4) role.start_dir++;
						else role.start_dir=1;
					draw_map(map);
					draw_role();
					break;
				case 'color':
					var v=expression_value(pg.op2);
					if(v>=0 && v<=7) map.map[role.starty][role.startx]=v;
					draw_map(map);
					draw_role();
					break;
				case 'read'://读取数据
					for(var i=0;i<map.data.length;i++){
						if(map.data[i].x==role.startx && map.data[i].y==role.starty){
							set_variable(pg.op2,map.data[i].value);
							//alert(map.data[i].value);
						}
					}
					break;
				case 'write'://写入数据
					var v=expression_value(pg.op2);
					eval('var _temp='+map.suanfa+';');
					if(_temp==v){
						
					}
					break;
				case 'jump'://跳
					jump_func();
					draw_map(map);
					draw_role();
					break;
				case 'check'://检测地雷
					var v=check_bomb();
					set_variable(pg.op2,x);
					break;
				case 'remove'://排除地雷
					remove_bomb();
					draw_map(map);
					draw_role();
					break;
			}
			icode++;
			setTimeout(interpret,1000);
		}
	}else {
		//检查是否完成任务
		var success=false;
		if(map.success.indexOf('pos')!=-1){
			if(role.start_dir==map.end_dir && role.startx==map.endx && role.starty==map.endy)
				success=true;
		}
		if(success){
			alert('任务完成');
		}else{
			alert('任务失败');	
		}
		//alert('程序执行完成');
	}
}

function jump_func(){
	switch(role.start_dir){
		case 1:
			x=role.startx;y=role.starty-1;
			break;
		case 2:
			x=role.startx+1;y=role.starty;	
			break;
		case 3:
			x=role.startx;y=role.starty+1;
			break;
		case 4:
			x=role.startx-1;y=role.starty;
			break;
		}
	if(map.map[role.starty][role.startx]>=0 && map.map[role.starty][role.startx]<=7){
		if(map.map[y][x]==8){
			role.startx=x;role.starty=y;
		}
	}
	if(map.map[role.starty][role.startx]==8){
		if((map.map[y][x]==9) || (map.map[y][x]>=0 && map.map[y][x]<=7)){
			role.startx=x;role.starty=y;
		}
	}
	if(map.map[role.starty][role.startx]==9){
		if(map.map[y][x]==8){
			role.startx=x;role.starty=y;
		}	
	}
}

function remove_bomb(){
	if(map.remove_bomb_count>0){
		map.remove_bomb_count--;
		var x,y;
		switch(role.start_dir){
		case 1:
			x=role.startx;y=role.starty-1;
			break;
		case 2:
			x=role.startx+1;y=role.starty;	
			break;
		case 3:
			x=role.startx;y=role.starty+1;
			break;
		case 4:
			x=role.startx-1;y=role.starty;
			break;
		}
		for(var i=0;i<map.bomb.length;i++){
			if(x==map.bomb[i].x && y==map.bomb[i].y && map.bomb[i].able==1){
				map.bomb[i].able=0;
			}
		}
	}else{
		alert('您的排雷工具已经用完！');	
	}
}

function go_func(){
	if(check_bomb()==1) {
		return false;
	}
	switch(role.start_dir){
	case 1:
			if(role.starty>0)
			if(map.map[role.starty-1][role.startx]>=0 && map.map[role.starty-1][role.startx]<=7)
			role.starty--;
		break;
	case 2:
			if(role.startx<map.mapwidth-1)
			if(map.map[role.starty][role.startx+1]>=0 && map.map[role.starty][role.startx+1]<=7)
			role.startx++;
		break;
	case 3:
			if(role.starty<map.mapheight-1)
			if(map.map[role.starty+1][role.startx]>=0 && map.map[role.starty+1][role.startx]<=7)
			role.starty++;

		break;
	case 4:
			if(role.startx>0)
			if(map.map[role.starty][role.startx-1]>=0 && map.map[role.starty][role.startx-1]<=7)
			role.startx--;

		break;
	}
	return true;
}

function check_bomb(){
	var x,y;
	switch(role.start_dir){
	case 1:
		x=role.startx;y=role.starty-1;
		break;
	case 2:
		x=role.startx+1;y=role.starty;	
		break;
	case 3:
		x=role.startx;y=role.starty+1;
		break;
	case 4:
		x=role.startx-1;y=role.starty;
		break;
	}
	for(var i=0;i<map.yun.length;i++){
		if(x==map.yun[i].x && y==map.yun[i].y){
			map.yun[i].able=0;
		}
	}
	for(var i=0;i<map.bomb.length;i++){
		if(x==map.bomb[i].x && y==map.bomb[i].y && map.bomb[i].able==1){
			//炸弹
			return 1;
		}
	}
	return 0;
}

function get_condition(cond){
	var a,b;
	if(cond.indexOf('==')!=-1) {
		a=cond.split('==');
		b='==';
	}
	else if(cond.indexOf('>=')!=-1) {a=cond.split('>=');b='>=';}
	else if(cond.indexOf('<=')!=-1) {a=cond.split('<=');b='<=';}
	else if(cond.indexOf('>')!=-1) {a=cond.split('>');b='>';}
	else if(cond.indexOf('<')!=-1) {a=cond.split('<');b='<';}
	if(a.length=3){
		var v1=expression_value(a[0]);
		var v2=expression_value(a[1]);
		switch(b){
			case '==':return v1==v2;
			case '>=':return v1>=v2;
			case '<=':return v1<=v2;
			case '>':return v1>v2;
			case '<':return v1<v2;	
		}
	}else{
		alert('条件表达式错误');	
	}
}

function draw_map(map){
	//console.log(map.mapheight);
	for(var i=0;i<map.mapwidth;i++){
		for(var j=0;j<map.mapheight;j++){
			switch(map.map[j][i]){
			case 23:
				ctx.drawImage(img[0],i*map.cell_width,j*map.cell_height,map.cell_width,map.cell_height);
				break;
			case 8:
			case 9:
				var x=i*map.cell_width;
				var y=j*map.cell_height;
				ctx.fillStyle=colors[0];
				ctx.fillRect(x,y,map.cell_width,map.cell_height);
				ctx.strokeStyle='#CFCFCF';
				ctx.strokeRect(x,y,map.cell_width,map.cell_height);
				ctx.drawImage(img[map.map[j][i]+5],x,y,map.cell_width,map.cell_height);
				break;	
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
			case 6:
			case 7:
				var x=i*map.cell_width;
				var y=j*map.cell_height;
				ctx.fillStyle=colors[map.map[j][i]];
				ctx.fillRect(x,y,map.cell_width,map.cell_height);
				ctx.strokeStyle='#CFCFCF';
				ctx.strokeRect(x,y,map.cell_width,map.cell_height);
				break;	
			}
		}
		
	}
	//画数据点
	for(var i=0;i<map.data.length;i++){
		ctx.drawImage(img[12],map.data[i].x*map.cell_width,map.data[i].y*map.cell_height,map.cell_width,map.cell_height);
	}
	//画炸弹
	for(var i=0;i<map.bomb.length;i++){
		if(map.bomb[i].able==1)
		ctx.drawImage(img[4],map.bomb[i].x*map.cell_width,map.bomb[i].y*map.cell_height,map.cell_width,map.cell_height);
	}
	//画云
	for(var i=0;i<map.yun.length;i++){
		if(map.yun[i].able==1)
		ctx.drawImage(img[11],map.yun[i].x*map.cell_width,map.yun[i].y*map.cell_height,map.cell_width,map.cell_height);
	}
	//画出起点和终点
	ctx.drawImage(img[9],map.startx*map.cell_width,map.starty*map.cell_height,map.cell_width,map.cell_height);
	ctx.drawImage(img[10],map.endx*map.cell_width,map.endy*map.cell_height,map.cell_width,map.cell_height);
}

function draw_role(){
	var x=role.startx*map.cell_width,y=role.starty*map.cell_height;
	switch(role.start_dir){
		case 1:
			ctx.drawImage(img[5],x,y,map.cell_width,map.cell_height);
			break;
		case 2:
			ctx.drawImage(img[6],x,y,map.cell_width,map.cell_height);
			break;
		case 3:
			ctx.drawImage(img[7],x,y,map.cell_width,map.cell_height);
			break;
		case 4:
			ctx.drawImage(img[8],x,y,map.cell_width,map.cell_height);
			break;	
	}
}
</script>
</body>
</html>
